<?php
/**
 * basic reference book
 *
 * ::$entity contains name of reference entity in english
 * ::$captions contains names of pages
 * <code>
 * <?php
 * $this->captions = array(
 * 	'index' => __('Entities reference book'),
 * 	'add' => __('Add entity'),
 * 	'edit' => __('Edit entity'),
 * 	'remove' => __('Remove entity'),
 * 	);
 * ?>
 * </code>
 * autogenerating using entity by default
 *
 * ::$form — formx instance
 * ::$filter — filter instance
 * forms and filters, if not deactivated (active by default), uses fields from files.
 * for forms are:
 * 	page_add		{controller_views_path}/.forms/add.php,
 * 	page_edit		{controller_views_path}/.forms/edit.php,
 * 	page_someother	{controller_views_path}/.forms/someother.php
 * and for filters are:
 * 	page_index		{controller_views_path}/.forms/filter.index.php,
 * 	page_someother	{controller_views_path}/.forms/filter.someother.php.
 *
 * {controller_views_path} is something like /views/simple/ for simple_controller
 * and /views/admin/section/subsection/ (for section_subsection_admin_controller.
 *
 * in ref just one form and one filter cuz for one page (page_index, for example)
 * we have just one form and one filter (or dont have it).
 *
 * ::$model — some object extends model
 * it used for default add, edit, remove handlers (add_item, edit_item, remove_item)
 * all of them would be verified and basically does by model verification (all is true if model is model)
 *
 * $this->permissions is map of key=>roles (comma delimited string).
 * something like:
 * <code>
 * <?php
 * array(
 * 	'index' => 'manager,boss',
 * 	'add' => 'manager,boss',
 * 	'edit' => 'boss',
 * 	// and so on
 * );
 * ?>
 * </code>
 * and for some special functionality there is pseudo-pages in ref_controller:
 * 	:filter		// show filter only for selected roles
 * 	:pagination // show pagination ...
 * nulled (unsetted) properties makes page public
 *
 * lists variable is a map with named references of another entities which used in filters and forms
 * ::init_lists basically can be unused. it called once before assembling form or filter for load lists.
 *
 * ::init_form used for form generation
 * ::init_filter analogically for filter
 *
 * ::$columns is a map with titles (other maps):
 * <code>
 * 'id' => array(
 * 	'text' => 'Lalala', // *
 * 	'title' => '', //
 * 	'order' => 'id#', // ?
 * 	'group' => 'integer', // ?
 * 	);
 * </code>
 *
 * @version:	0.1
 * @package:	global
 * @author:		alex_ez
 * @created:	17.02.2010 17:22
 * @copyright:	(c) 2010 alex <qfox@ya.ru> yaroshevich aka alex_ez. All rights reserved.
 */

abstract class refbook_controller extends controller
{
	/**
	 * Single entity title
	 * @var string
	 */
	protected $entity_title = 'item';

	/**
	 * Multiple entities title
	 * @var string
	 */
	protected $entities_title = 'items';

	/**
	 * Contains captions for pages and buttons
	 * @var array of strings
	 */
	protected $captions = null;

	/**
	 * Points to a row field key represents title or name
	 * @var string
	 */
	protected $item_title_key = 'title';

	// form and filter
	/**
	 * Form object
	 * @var formx
	 */
	protected $form = null;

	/**
	 * Filter object. Used in lists
	 * @var filter
	 */
	protected $filter = null;

	/**
	 * Default filter mode
	 * @var string
	 */
	protected $filter_mode = 'limited';

	/**
	 * Pager object. Used in lists
	 * @var xpager
	 */
	protected $pager = null;

	/**
	 * Per pages quantities. Comma delimited list
	 * @var string
	 */
	protected $pager_per_pages = '15,50,150,500';

	/**
	 * Sorter object. Used in lists
	 * @var xsorter
	 */
	protected $sorter = null;

	/**
	 * Default sorting
	 * @var string
	 */
	protected $order = 'id-';

	/**
	 * Lists for forms and filters
	 * @var array of arrays
	 */
	protected $lists = null;

	/**
	 * Model for default get_items, get_item, add_item, edit_item, remove_item methods
	 * @var model
	 */
	protected $model = null;

	/**
	 * Default columns. Would be defined in on_initialize method
	 * @var array
	 */
	protected $columns = array(
		'id' => array(
			'text' => 'Id',
			'order' => true,
		),
		'tag' => array(
			'text' => 'Tag',
			'title' => 'Tag tag',
			'order' => 'tag+',
		),
		'title' => 'Title',
		'item_id' => array(
			'text' => 'Group',
			'order' => '(select `name` from items where `id` = `item_id`)+'
		),
		'user_id' => array(
			'text' => 'User',
		),
	);

	/**
	 * Default assets array
	 * @var array
	 */
	protected $assets = array( );

	/**
	 * Permissions for page access
	 * @var array of strings
	 */
	protected $permissions = array(
		'index' => null,
		'show' => null,
		'add' => null,
		'edit' => null,
		'remove' => null,
		);

	/**
	 * Fieldsets. Would be defined in on_initialize
	 * @var array of mixed
	 */
	protected $fieldsets;

	/**
	 * Fieldset template
	 * @var array
	 * @deprecated
	 */
	protected $default_fieldset =
"{
'id':	{type:static,label:'id',value:''},
'tag':	{type:static,label:'Tag',value:''},
'value':{type:string,label:'Value',required:true},
}";


	/**
	 * Initialization hook
	 * 
	 * Contains default initialization algorithm
	 * Usually called after custom defines
	 *
	 * @see walker
	 * @param walker $walker
	 */
	public function on_initialize( &$walker )
	{
		$walker->refbook = true;
		parent::on_initialize( $walker );

		$inner_url = $this->inner_url();
//var_dump( $inner_url ); die;

		$this->columns = is_string($this->columns) && mb_strlen($this->columns)
			? json::decode($this->columns)
			: $this->columns;

		$this->fieldsets += array(
			'add' => true,
			'edit' => true,
			'index#filter' => array(
				'id' => array( 'type' => 'text', 'label' => __('ID'), 'class' => 'short' ),
				'title' => array( 'type' => 'text', 'label' => __('Title') ),
				),
			);

		if (empty($this->captions))
		$this->captions = array(
			'title'		=> __(mb_ucfirst($this->entities_title)),
			'index'		=> __(sprintf('%s reference book',mb_ucfirst($this->entities_title))),

			'show'		=> __(sprintf('Show %s',$this->entity_title)),
			'add'		=> __(sprintf('Create %s',$this->entity_title)),
			'edit'		=> __(sprintf('Modify %s',$this->entity_title)),
			'remove'	=> __(sprintf('Delete %s',$this->entity_title)),

			'showing'	=> __(sprintf('%s "%%s"',mb_ucfirst($this->entity_title))),
			'adding'	=> __(sprintf('New %s',$this->entity_title)),
			'editing'	=> __(sprintf('Modifing %s "%%s"',$this->entity_title)),
			'removing'	=> __(sprintf('Deleting %s "%%s"',$this->entity_title)),
			);

		$this->init_crumbs();
	}

	/**
	 * Initialization for breadcrumbs
	 *
	 * @see basic_controller::$crumbs
	 * @see basic_controller::get_crumbs
	 */
	protected function init_crumbs()
	{
		$this->crumbs[$this->inner_url('index')] = $this->captions['title'];
	}

	/**
	 * Initialization for columns
	 *
	 * @see $columns
	 */
	protected function init_columns()
	{
		foreach ($this->columns as $k => $col)
		{
			if (is_string($col))
				$col = $this->columns[$k] = array( 'text' => $col, 'order' => true );

			if (!isset($col['text']) || empty($col['text']))
				$this->columns[$k]['text'] = str_replace( '_', ' ', mb_convert_case($k, MB_CASE_TITLE) );				

			if (isset($col['title']) && !empty($col['title']) )
				$this->columns[$k]['title'] = str_replace('|','!',$this->columns[$k]['title']);

			$this->columns[$k]['text'] = str_replace('|','!',$this->columns[$k]['text']);

			if (isset($col['order']) && $col['order'] === true)
				$this->columns[$k]['order'] = $k.'+';
		}
	}

	/**
	 * Initialization for form
	 *
	 * @see $form
	 */
	protected function init_form()
	{
		if ($this->form || $this->form === false)
			return;

		$this->form = new formx( current_url() );
		$this->form->separate_postfiles();

		$this->init_lists();
		if (is_array($this->lists))
			$this->form->set_lists( $this->lists );

		$fieldset = $this->get_fieldset( );

		// set premade fieldset from controller
		if(is_array($fieldset) || (is_string($fieldset)&&$fieldset[0]!=='/'))
			$this->form->set_fieldset($fieldset);

		// try to load fieldset from file
		elseif( is_string($fieldset) && $fieldset[0] === '/' )
			$this->form->load_fields( trim($fieldset,'/') );

		// load fields from file with autosearch
		elseif( $fieldset === true )
			$this->form->load_fields();
	}

	/**
	 * Filter creation
	 *
	 * @see $filter
	 */
	protected function init_filter()
	{
		if ($this->filter || $this->filter === false)
			return;

		$inner_url = $this->inner_url( );
		$fieldset = $this->get_fieldset( '#filter' );

		$this->filter = new filterx( $inner_url );
		$this->filter->set_option( 'mode', $this->filter_mode );
		$this->filter->set_option( 'limits', $this->pager_per_pages );
		$this->filter->set_fieldset( $fieldset );

		$this->init_lists();
		if (is_array($this->lists))
			$this->filter->set_lists( $this->lists );
	}

	/**
	 * Pager creation
	 *
	 * @see $pager
	 */
	protected function init_pager()
	{
		$disabled = $this->pager === false;

		if (!$this->pager)
			$this->pager = new xpager();

		if ($disabled)
			return $this->pager->disable();

		$this->pager->set_options(array(
			'at_start' => 2,
			'at_end' => 2,
			'request_var' => 'get_page',
			'per_page' => $this->get_items_per_page(),
			));
	}

	/**
	 * Pager compilation
	 *
	 * @see $pager
	 */
	protected function build_pager()
	{
		if ($this->pager->disabled())
			return;

		$this->pager->set_options(array(
			'total_items' => $this->get_found_rows(),
			'url_pattern' => $this->inner_url('index', $this->filter->get_query( array( 'order' => $this->sorter->get_order(), 'page' => '' ) ) . ':page')
			));
		
		$this->pager->build();
	}

	/**
	 * Sorter creation
	 *
	 * @see $sorter
	 */
	protected function init_sorter()
	{
		$this->init_columns();

		if (!$this->sorter)
			$this->sorter = new xsorter();

		$this->sorter->set_default($this->order);
		$this->sorter->set_columns($this->columns);
		$this->sorter->set_directions(array(
			'+' => html_element( 'span', array( 'text' => '↓', 'title' => __('Ascending sort') ) ),
			'-' => html_element( 'span', array( 'text' => '↑', 'title' => __('Descending sort') ) ),
			));
	}

	/**
	 * Lists fetching
	 *
	 * @see $lists
	 */
	protected function init_lists()
	{
		if ($this->lists || $this->lists === false)
			return;

		$this->lists = array();
	}

	/**
	 * Fetch some preinitialized list
	 *
	 * @param string $k
	 * @return array
	 */
	public function get_list($k) {
		return !isset($this->lists[$k]) ? array() : $this->lists[$k];
	}

	/**
	 * Fetch all preinitialized lists
	 *
	 * @return array
	 */
	public function get_lists() {
		return array_filter((array)($this->lists));
	}


	/**
	 * Info callback. Return some basic data for indexes
	 * 
	 * @return array array with title, caption and href keys
	 */
	public function info() {
		$r = array();
		$r['title'] = $this->captions['title'];
		$r['caption'] = $this->captions['index'];
		$r['href'] = $this->inner_url();
		return $r;
	}




	/**
	 * Gets items via model
	 * 
	 * @param mixed $w array or string
	 * @param string $o order
	 * @param mixed $limit array or 2 integers or integer
	 * @return array items
	 */
	protected function get_items( $w = '', $o = null, $l = null )
	{
		if (!$this->validate_model())
			return array();

		$f = '*';
		foreach ($this->get_columns() as $k => $c) {
			if (empty($c['value'])) continue;
			$f .= sprintf( ', %s as `%s`', $c['value'], $k );
		}
		//var_dump( compact( 'f', 'w', 'o', 'l' ) );
		$r = $this->model->fby( $f, $w, $o, $l, -1 );
		//var_dump( $w, $o, $l, $this->get_found_rows(), db::last_query() );
		return $r;
	}

	/**
	 * Gets found rows count
	 *
	 * @return int
	 */
	protected function get_found_rows()
	{
		if (!$this->validate_model())
			return false;

		return $this->model->get_found_rows();
	}

	/**
	 * Gets item
	 *
	 * @param int $id item identificator
	 * @return array item data
	 */
	protected function get_item( $id )
	{
		if (!$this->validate_model())
			return false;

		return $this->model->get( $id );
	}


	/**
	 * Add item handler (one of default modify handlers)
	 *
	 * @param array $d posted data
	 * @param array $f posted files
	 * @return mixed false or item id
	 */
	public function add_item( $d, $f = null )
	{
		if (!$this->validate_add($d))
			return false;

		if (!empty($f)) {
			var_dump( $d, $f );
			die('Something went wrong in add_item');
		}

		return $this->model->add( $d );
	}

	/**
	 * Edit item handler (one of default modify handlers)
	 *
	 * @param integer $id item id
	 * @param array $d posted data
	 * @param array $f posted files
	 * @return mixed false or integer (count of modified rows) 0 or 1
	 */
	public function edit_item( $id, $d, $f = null )
	{
		if (!$this->validate_edit($id, $d))
			return false;

		if (!empty($f)) {
			var_dump( $id, $d, $f );
			die('Something went wrong in edit_item. Posting files for editing is not supported now. Sorry');
		}

		return $this->model->edit( $id, $d );
	}

	/**
	 * Remove item handler (one of default modify handlers)
	 *
	 * @param integer $id item id
	 * @param array $d item data from database if exists
	 * @return mixed false, 0 or 1
	 */
	public function remove_item( $id, $d = null )
	{
		if (!$this->validate_remove( $id, $d = null ))
			return false;

		return $this->model->remove( $id );
	}


	/**
	 * Validation default handler for model
	 *
	 * @see $model
	 */
	public function validate_model( ) {
		return $this->model instanceof model;
	}

	/**
	 * Add validation default handler
	 *
	 * @see $model
	 */
	public function validate_add( $d = null ) {
		return $this->validate_model();
	}

	/**
	 * Edit validation default handler
	 *
	 * @see $model
	 */
	public function validate_edit( $id, $d ) {
		return $this->validate_model();
	}

	/**
	 * Remove validation default handler
	 *
	 * @see $model
	 */
	public function validate_remove( $id, $d = null ) {
		return $this->validate_model();
	}




	/**
	 * Where item fetcher
	 *
	 * @see $filter
	 * @see model::by()
	 * @return array where condition array for model by method
	 */
	public function get_items_where() // by filter
	{
		return $this->filter->get_where();
	}

	/**
	 * Fetch selected limit value for user
	 *
	 * @see $filter
	 * @return integer
	 */
	public function get_items_per_page()
	{
		return $this->filter->get_limit_value();
	}

	/**
	 * Returns rendered selected order coulums with directions
	 *
	 * @see $sorter
	 * @see $columns
	 * @see model::by()
	 * @return string
	 */
	public function get_items_order()
	{
		return $this->sorter->get_order();
	}

	/**
	 * Returns limit by selected limit quantity in filter and page in pager
	 *
	 * @see $pager
	 * @see $columns
	 * @see model::by()
	 * @return array two integer. from and quantity
	 */
	public function get_items_limit()
	{
		return (is_object($this->pager) ? $this->pager->get_limit() : null);
	}

	/**
	 * Returns current page
	 */
	public function get_current_page()
	{
		return (is_object($this->pager) ? $this->pager->get_current_page() : 1);
	}

	/**
	 * Fetch preinitialized columns
	 *
	 * @see $columns
	 * @return array columns (map)
	 */
	public function get_columns()
	{
		return $this->columns;
	}

	/**
	 * Fetch preinitialized columns keys
	 * @return array keys of columns
	 */
	public function get_columns_keys()
	{
		return array_keys($this->columns);
	}

	/**
	 * Render columns for table list
	 *
	 * @return array rendered for output columns
	 */
	public function render_columns()
	{
		// cols
		$filter_path = $this->inner_url_path();
		$filter_query = $this->filter ? $this->filter->get_query() : '';
		$filter_query .= !empty($filter_query)?'&':'';

		$r = array();
		foreach( $this->get_columns() as $k => $col ) {

			$c = array();
			if ( $k == 'buttons' ) {
				$r[$k] = $c;
				continue;
			}

			$c['text'] = $col['text'];
			$c['width'] = (!empty($col['width']) ? $col['width'] : '');

			if ($this->sorter->is_sortable($k)) {
				$c['href'] = sprintf( '%s?%sorder=%s', $filter_path, $filter_query, $this->sorter->get_href_order($k) );
			}

			if ($this->sorter->is_sorting($k)) {
				$c['class'] = pick(@$col['sorting_class'],'common');
				$c['after'] = ' ' . $this->sorter->get_direction_sign($k);
			} elseif (!empty($col['class'])) {
				$c['class'] = $col['class'];
			}

			if (!empty($col['title']))
				$c['title'] = $col['title'];

			$r[$k] = $c;
		}

		return $r;
	}

	/**
	 * Default index page
	 *
	 * @param array $p request parameters
	 * @param array $o page options
	 * @return mixed array with page data for view or rendered string
	 */
	public function page_index( $p, $o )
	{
		$this->check_roles( 'index' );
		$this->init_filter();
		$this->init_sorter();
		$this->init_pager();

		$r = array();

		// filter
		$r['filter'] = $this->filter;
		$r['pager'] = $this->pager;

		// limit with paging
		$where = $this->get_items_where();
		$limit = $this->get_items_limit();
		$order = $this->get_items_order();
		$this->order = $order;

		// store for remove and put to orders
		$page = $this->get_current_page();
		$this->visitor->storedquery = $this->filter->get_query( compact( 'order', 'page' ) );

		// items
		$r['items'] = $this->get_items( $where, $order, $limit );
		if (!is_array($r['items']))
			throw new Exception('refbook::get_items must return an array');

		// postparations
		$this->build_pager();
		$r['count'] = $this->get_found_rows();

		$format = formatter::instance();
		// +buttons
		$buttons_count = 0;
		$colkeys = $this->get_columns_keys();

		foreach( array_keys( $r['items'] ) as $k )
		{
			$row = $r['items'][$k];

			$r['items'][$k] = array();
			foreach( $colkeys as $col ) {
				$v = isset($row[$col]) ? $row[$col] : null;

				// prerender
				if (isset($this->columns[$col]['render'])) {
					$render = $this->columns[$col]['render'];
					if ($render===true) {
						$render = array($this, 'render_field_'.$col);
					} elseif (is_string($render)) {
						if (method_exists($this,$render)) {
							$render = array($this,$render);
						} elseif (method_exists($this,'render_'.$render)) {
							$render = array($this,'render_'.$render);
						} elseif (!is_callable($render)) {
							$render = null;
						}
					}

					$v = is_callable($render) ? call_user_func($render, $k, $row, $col) : '?';
				}

				// format handler
				if (isset($this->columns[$col]['format']))
					$v = $format->{$this->columns[$col]['format']}( $v );

				// anchor handler
				if (isset($this->columns[$col]['anchor'])) {
					$a = array();
					if (is_string($this->columns[$col]['anchor'])) {
						$a['href'] = $this->inner_url($this->columns[$col]['anchor'], $row);

					} elseif (is_array($this->columns[$col]['anchor'])) {
						$a = $this->columns[$col]['anchor'];
						if (!empty($a['href']))
							$a['href'] = subst($a['href'], $row);
						if (isset($a['url']) && $this->url_patterns($a['url'])) {
							$a['href'] = $this->inner_url($a['url'], $row);
							unset($a['url']);
						}
						if (!empty($a['text']))
							$a['text'] = subst($a['text'], $row);
					}
					if (empty($a['text']))
						$a['text'] = $v;

					$v = html_a( $a );
				}

				$r['items'][$k][$col] = $v;
			}

			$r['items'][$k]['buttons'] = $this->render_row_controls( $r['items'][$k], $k );
			$buttons_count = max( $buttons_count, substr_count( $r['items'][$k]['buttons'], '"ibutton' ) );
		}


		$r['cols'] = $this->render_columns();

		if ($buttons_count>0)
			$r['cols']['buttons'] = array( 'text' => ' ', 'width' => intval($buttons_count*20+5).'px' );
		else
			unset($r['cols']['buttons']);

		$info = $this->info(1);
		$r['title'] = isset($info['caption']) ? $info['caption'] : sprintf('%s «%s»',__('Reference book'),__($this->entities_title));
		$r['header_after'] = $this->check_roles('add',1)
			? html_a( $this->inner_url('add'), $this->captions['add'], 'ibutton add', $this->captions['add'] )
			: '';

		$r['assets'] = $this->assets;

		$r['template'] = 'refbook/list.php';

		return $r;
	}


// <editor-fold defaultstate="collapsed" desc="Other methods">
	
	/**
	 * Renders controls cell for each row
	 *
	 * @param array $v list item
	 * @param key $k list key
	 * @param string $mode
	 * @return string html string for controls column
	 */
	protected function render_row_controls( $v, $k = null, $mode = null )
	{
		$r = '';

		if( is_null( $mode ) ) {
			$mode = '';
			if( !$this->validate_remove( $k, $v ) ) $mode .= 'noremove';
			if( !$this->validate_edit( $k, $v ) ) $mode .= 'noedit';
		}

		if( is_null( $mode ) || stripos( $mode, 'noedit' ) === false )
			$r .= html_a( $this->inner_url('edit',$v['id']), $this->captions['edit'], 'ibutton edit', $this->captions['edit'] );
		if( is_null( $mode ) || stripos( $mode, 'noremove' ) === false )
			$r .= html_a( $this->inner_url('remove',$v['id']), $this->captions['remove'], 'ibutton delete confirm', $this->captions['remove'] );

		return $r;
	}

// </editor-fold>


	public function page_show( $p, $o )
	{
		if( !$this->check_roles('show',1) )
			$this->visitor->flash( 'reference_controller', '<p class="error">' . __('You have no permissions to look at item') . '</p>', $this->inner_url( 'index' ) );

		$id = isset($p['id']) ? $p['id'] : reqi()->g_id;
		if (empty($id))
			$this->visitor->flash( 'reference_controller', '<p class="error">' . __('Item not found. Sorry') . '</p>', $this->inner_url( 'index' ) );

		$i = $this->get_item($id);

		$title = $i[$this->item_title_key];
		$this->crumbs[] = $title;

		$r = array(
			'title' => sprintf( $this->captions['showing'], $title ),
			'template' => 'refbook/show.php',
			'item' => $i,
			'backref' => html_a( $this->inner_url(), $this->captions['index'] ),
			);

		return $r;
	}


	public function page_add( $p, $o )
	{
		if( !$this->check_roles('add',1) )
			$this->visitor->flash( 'reference_controller', '<p class="error">' . __('You have no permissions to add item') . '</p>', $this->inner_url( 'index' ) );

		$this->crumbs[] = $this->captions['add'];

		$id = 'new';
		$this->init_form();

		$d = array(); // ?

		$this->form->set_values($d);

		$r = array( );
		$r['title'] = $this->captions['adding'];
		$r['form'] =& $this->form;
		$r['template'] = 'refbook/add.php';

		if($this->form->validate()) {

			$data = $this->form->get_postdata();
			$files = $this->form->get_postfiles();

			if( !$this->validate_add( $data, $files ) ) {
				$this->visitor->flash( 'reference_controller', '<p class="error">' . __('Cannot store item') . '</p>' );
				return $r;
			}

			$id = $this->add_item( $data, $files );

			if( !$id ) {
				$this->visitor->flash( 'reference_controller', '<p class="error">' . __('An error occurred while saving') . '</p>' );
				return $r;
			}

			$this->visitor->flash( 'reference_controller', '<p class="updated">' . __('The data successfully saved') . '</p>', $this->inner_url('index') );
		}

		$r['assets'] = $this->assets;

		return $r;
	}

	public function page_edit( $p, $o )
	{
		if( !$this->check_roles('edit',1) )
			$this->visitor->flash( 'reference_controller', '<p class="error">' . __('You have no permissions to edit item') . '</p>', $this->inner_url('index') );

		$this->crumbs[] = $this->captions['edit'];

		$id = (int)(reqi()->g_id);
		$this->init_form();

		$d = $this->get_item( $id );

		if( !$d )
			$this->visitor->flash( 'reference_controller', '<p class="error">' . __('Item not found!') . '</p>', $this->inner_url('index') );

		if( !$this->validate_edit( $id, $d ) )
			$this->visitor->flash( 'reference_controller', '<p class="error">' . __('Item cannot be modified') . '</p>', $this->inner_url('index') );

		$this->form->set_values( $d );

		$r = array( );
		$r['title'] = sprintf( $this->captions['editing'], $d[$this->item_title_key] );
		$r['form'] =& $this->form;
		$r['template'] = 'refbook/edit.php';

		if($this->form->validate()) {

			$data = $this->form->get_postdata();
			$files = $this->form->get_postfiles();

			$eo = $this->edit_item( $id, $data, $files );

			// returns 0 if no fields for update as well as error occurred
			if( !$eo ) {
				$this->visitor->flash( 'reference_controller', '<p class="error">' . __('An error occurred while data saving') . '</p>' );
				return $r;
			}

			$this->visitor->flash( 'reference_controller', '<p class="updated">' . __('The data successfully saved') . '</p>', $this->inner_url( 'index' ) );
		}

		$r['assets'] = $this->assets;

		return $r;
	}

	/*public function ajax_edit( $z )
	{
		$this->check_roles( 'edit' );
		
		$id = Request::instance( )->g_id;
		$id = ( $id == 'new' || $id == -1 ) ? 'new' : (int)($id);
		$new = ( $id === 'new' );
		
		// fetch post data if data is bogus return error
		try { $p = $this->form->get_post_data( ); } catch( form_exception $e ) { return array( 'error' => $e->getMessage() ); }
		
		if( isset( $this->empty['tag'] ) && empty( $p['tag'] ) )
			$p['tag'] = strtolower( texo::latinate( pick( @$p['name'], @$p['title'], @$p['value'], rand(10000,99999) ) ) );
		
		// plain text and desc fields
		//foreach( array( 'text', 'desc' ) as $kavo ) if( isset( $this->empty[ $kavo ] ) ) $p[ $kavo ] = texo::plain( @$p[ $kavo ] );
		
		$errors = array( );
		$p = $this->handle_row( 'beforeajax'.($new ? 'add' : 'edit'), $p, $id );
		if( !$this->is_editable( $p, $id ) ) return array( 'error' => __('Cant store') );
		if( $new ) {
			$id = $this->model->add( $p );
			if( !$id ) $errors[] = __('Not stored');
		} else {
			$eo = $this->model->edit( $id, $p );
			if( $eo!=1 ) $errors[] = __('Not stored');
		}
		
		if( $id > 0 )
		{
			$error = $this->handle_row( 'ajax'.($new ? 'add' : 'edit'), $p, $id );
			if( is_subclass_of( $this->that, 'controller' ) ) // linked
				$errors[] = is_null( $error ) ? 'Data linked' : $error;
		}
		
		if( empty($errors) ) $errors[] = __('Stored!');
		
		return array( 'message' => join( ', ', $errors ), 'location' => '../', 'confirm' => true );
	}*/

	/**
	 * Page Remove
	 */
	public function page_remove( $p, $o )
	{
		$this->check_roles( 'remove' );

		$this->crumbs[] = $this->captions['remove'];

		$id = (int)request::instance()->g_id;
		$item = $this->get_item($id);
		
		if( !$item ) {
			$r['out'] = __('Item not found');

		} elseif( $this->validate_remove( $id, $item ) ) {
			$removed = $this->remove_item( $id );
			$r['out'] = sprintf(_n('%d item removed', '%d items removed', $removed), $removed); // ?

		} else {
			$r['out'] = __('Non-removable item');
		}

		$r['title'] = sprintf( $this->captions['removing'], pick( $item[$this->item_title_key], '?' ) );
		$r['template'] = 'admin/__out.tpl';

		$back_url = $this->inner_url( 'index' );
		$r['links'] = array(
			array( 'href' => $back_url . '?' . $this->visitor->storedquery, 'title' => __( 'Back to filtered list' ) ),
			array( 'href' => $back_url, 'title' => __( 'Back to list index' ) ),
			);

		return $r;
	}


	/**
	 * _roles checker
	 *
	 * @param string $tag
	 * @param boolean $return
	 * @return boolean if $return param is true you get boolean on return. else it will send forbidden headers.
	 */
	protected function check_roles( $tag = null, $return = false )
	{
		if( is_null( $this->haveroles ) && is_null( $this->allowed_roles ) )
			return true;

		if( is_null( $this->haveroles ) && !is_null( $this->allowed_roles ) )
			$this->haveroles = $this->visitor->roles->any( $this->allowed_roles );

		if( !is_null( $this->haveroles ) && $this->haveroles === true )
			return true;

		if( $return === true ) return false;

		$this->visitor->flash( 'reference_controller', '<p class="error">' . __('You have no permissions') . '</p>', $this->inner_url('index') );
	}

	public function get_fieldset( $url = null, $decode = false )
	{
		$r = null;
		$c_index_url = $this->inner_url_path('index');
		$c_inner_url = $this->inner_url_path();

		$index_url = 'index';
		$inner_url = trim( str_replace( $c_index_url, '', $c_inner_url ), '/' );
		if (!$inner_url) $inner_url = $index_url;

		$iurl = !is_null( $url ) ? $url : $inner_url;
		$editurl = preg_replace('#(add)#', 'edit', $iurl, 1);

		// special fieldsets
		$sharp = strpos($url,'#');
		if ($sharp === 0) {
			$url = $inner_url . $url;
		}

		//var_dump( compact( 'iurl', 'controller_inner_url', 'index_url', 'inner_url', 'sharp', 'url', 'c_index_url', 'c_inner_url' ) ); die;

		if ($sharp !== false && isset($this->fieldsets[$url])) {
			$r = $this->fieldsets[$url];

		// manually defined in class
		} elseif(isset($this->fieldsets[$iurl])) {
			$r = $this->fieldsets[$iurl];

		} elseif(isset($this->fieldsets[$editurl])) {
			$r = $this->fieldsets[$editurl];

		}

		if($url == '_default_')
			$r = $this->default_fieldset;

		return $decode ? json::decode( $r ) : $r;
	}

	public function get_inner_url( $ajax = false )
	{
		$url = str_replace( 'ajax/', '', request::instance()->get_url() );
		$url = parse_url( $url );
		$r = trim( $url['path'], '/' );

		if( $ajax )
			$r = '/ajax/'.$r.'/'.(@$url['query']?('?'.$url['query']):'');

		return $r;
	}




	/**
	 * Default checkbox render
	 */
	public function render_checkbox( $k, $v, $col ) {

		$input_id = 'row_' . $k . '_' . $col;
		$r = form_input(array(
			'id' => $input_id,
			'name' => $col . '[' . ( isset($v[$col]) ? $k : '' ) . ']',
			'type' => 'checkbox',
			'value' => isset($v[$col]) ? $v[$col] : $k,
			'class' => 'refbook-checkbox column-' . $col,
			));

		if (isset($v[$col]))
			$r = form_label($form_input . ' ' . $v[$col], array('for'=>$input_id));

		return $r;

	}




	/**
	 * Admin/public switcher
	 * @todo: maybe make frames configurable? or just set $this->frame after on_load (in init 4 exmpl) ?
	 */
	public function on_load( &$walker )
	{
		$r = parent::on_load( $walker );

		if ($this->ns($this) == 'admin') {
			$this->visitor->need_auth( 'admin', true );
			$this->frame = 'admin/!index.tpl';
		} else {
			$this->frame = '!index.tpl';
		}

		return $r;
	}

	public function block_fullmenu( $p )
	{
		if (strpos(request::instance()->get_url(),'/admin/')===false)
			return parent::block_fullmenu( $p );

		if ( !isset( $p['_'] ) )
			$p['_'] = array( );
		
		$o = array( );
		$o['extends'] = array( 'admin_controller', 'refbook_controller', 'reference_controller' );
		$o['suffix'] = '_admin_controller';
		$o['listcallback'] = 'get_admin_controllers';
		$o['urlbase'] = '/admin/';
		
		$p['_'] += $o;
		
		return parent::block_fullmenu( $p );
	}
	
}

